/* 
 *	Copyright (C) 2009-2010 miaohua
 *	http://code.google.com/p/lrcshower/
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "Stdafx.h"
#include <cmath>
#include <string>
#include "GdiPlusTextRender.h"
using namespace std;

int m_WidthOffset = 30;
int m_HeightOffset = 20;

///Construnction
CGdiplusTextRender::CGdiplusTextRender()
{
     // Initialize the gdiplus system
     GdiplusStartup(&m_GdiplusToken, &m_GdiplusStartupInput, NULL);
}

CGdiplusTextRender::~CGdiplusTextRender()
{
	 GdiplusShutdown(m_GdiplusToken);	
}

SIZE CGdiplusTextRender::GetLrcBoxSize(HWND RendWin, const tstring & LrcLine)
{
     SIZE ContainBox = {};
     // If the lrc is empty, just return the zero box
     if( LrcLine.size() == 0 )
         return ContainBox;

     // Lookout here, because the length of lrc may be longer than the window's length
     // in pre render stage, we must draw the bitmap according to maximum one between 
     // length of lrc and window's length
 	 FontFamily FontFamily(m_LrcFont.lfFaceName);
     StringFormat Format;
     Format.SetAlignment(Gdiplus::StringAlignmentNear);
     Format.SetLineAlignment(Gdiplus::StringAlignmentNear);
      
     GraphicsPath LrcPath;
     LrcPath.AddString(LrcLine.c_str(), LrcLine.size(),&FontFamily,GetFontStyle(&m_LrcFont),
                       GetWordSize(m_LrcFont.lfHeight),Point(0,0),&Format);

     // Get bounds for current lrc
     RectF BoxSize;
	 LrcPath.GetBounds(&BoxSize); 

     ContainBox.cx = (int)ceil(BoxSize.Width)*21.0f/20.0f;
     ContainBox.cy = (int)ceil(BoxSize.Height)*11.0f/10.0f;
     
     return ContainBox;
}

float CGdiplusTextRender::GetWordSize(LONG Height)
{
     HDC DeskDC = GetDC(NULL);
     Height = -MulDiv(72, m_LrcFont.lfHeight, GetDeviceCaps(DeskDC, LOGPIXELSY)); 
	 ReleaseDC(NULL, DeskDC);
     // Well, I don't know why gdi+'s AddString use "Word Unit" which
     // seems different from "Point Size", but the relation is 3/4 by my test
     // which means 100 "Point Size" gets 75 "Word Unit"
     return (4.0f*Height)/3.0f;
}

FontStyle CGdiplusTextRender::GetFontStyle(const LOGFONT* Font)
{
     LONG Style = FontStyleRegular;
     if( Font->lfWeight == FW_BOLD )
         Style = FontStyleBold;
     if( Font->lfItalic == TRUE )
         Style = Style|FontStyleItalic;
     if( Font->lfUnderline == TRUE )
         Style = Style|FontStyleUnderline;
    
     return (FontStyle)Style;   
}

bool CGdiplusTextRender::PreRender(HWND RendWin, const tstring & Lrc, bool IsFocus, bool IsCpyData, BYTE ** MemBuf)
{
     // Get the rectangle of window
     RECT Rect;
	 GetWindowRect(RendWin, &Rect);

	 // Get memory dc
	 HDC WinDC = GetDC(RendWin);
	 HDC WinMemDC = CreateCompatibleDC(WinDC);
	     
     // Lookout here, because the length of lrc may be longer than the window's length
     // in pre render stage, we must draw the bitmap according to maximum one between 
     // length of lrc and window's length
     int Width = max(Rect.right-Rect.left, m_LrcBoxSize.cx/*+m_WidthOffset*/);
     int Height = Rect.bottom-Rect.top;

	 // Create Dib section & Select it into memory dc 	
	 BITMAPINFO  bmi;
	 memset(&bmi, 0, sizeof(bmi));
	 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	 bmi.bmiHeader.biWidth= Width;
	 bmi.bmiHeader.biHeight= Height;
	 bmi.bmiHeader.biPlanes=1;
	 bmi.bmiHeader.biBitCount=32;
	 bmi.bmiHeader.biCompression=BI_RGB;
		
	 BYTE * MemBits = NULL;
	 HBITMAP BitMap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**)&MemBits, 0, 0);
     
     SelectObject(WinMemDC,BitMap);

     // Get bitmap's size in BYTE unit
	 int BitmapSize = bmi.bmiHeader.biWidth * bmi.bmiHeader.biHeight * 4;
           
     // Use gdi+  
     Graphics Graph(WinMemDC);

	 // Set mode
	 Graph.SetSmoothingMode(SmoothingModeAntiAlias);
	 Graph.SetInterpolationMode(InterpolationModeHighQualityBicubic);

	 // Paint the background
	 Color BgClr(m_BgTransparent, GetRValue(m_BgClr), GetGValue(m_BgClr), GetBValue(m_BgClr));
	 SolidBrush BrushBkgnd(BgClr);
	 Graph.FillRectangle(&BrushBkgnd,0,0,Width, Height);

     // Add string path
	 FontFamily Font(m_LrcFont.lfFaceName);
	 StringFormat Format;
     Format.SetAlignment(StringAlignmentCenter);
     Format.SetLineAlignment(StringAlignmentNear);
     float Offset = (Height-m_LrcBoxSize.cy)/2.0f;
     RectF LrcBox(0.0f,Offset,Width,Height-Offset);
     RectF UpperBox(0.0f,Offset, LrcBox.Width, Height/2.0f-Offset+1);
     RectF LowerBox(0.0f,Height/2.0f+1, LrcBox.Width, Height/2.0f);

     GraphicsPath Path;
	 Path.AddString(Lrc.c_str(), Lrc.size(),&Font,
                    GetFontStyle(&m_LrcFont),GetWordSize(m_LrcFont.lfHeight),LrcBox,&Format);

	 // Draw outline
     Pen OutLinePen(Color(155,215,215,215),4);
	 if( m_OutlineMode )
     {
         OutLinePen.SetLineJoin(LineJoinRound);
	     Graph.DrawPath(&OutLinePen,&Path);
     }

     // Shadow brush
     LinearGradientBrush ShadowBrush(
              PointF(0.0f,0.0f),PointF(0.0f,LrcBox.Height),
			  Color(30, 30, 30),
			  Color(30, 30, 30));
     // Focus brush
     LinearGradientBrush FocusBrush1(
			  //Point(Rect.left,Rect.top),Point(Rect.left,(Rect.top+Rect.bottom)/2),
              PointF(0.0f,Offset),PointF(0.0f,Height/2.0f+1),
			  Color(m_LrcTransparent,GetRValue(m_FocusUpperClr),GetGValue(m_FocusUpperClr),GetBValue(m_FocusUpperClr)),
			  Color(m_LrcTransparent,GetRValue(m_FocusMidClr),GetGValue(m_FocusMidClr),GetBValue(m_FocusMidClr)));
	        
	 LinearGradientBrush FocusBrush2(
			  //Point(Rect.left,(Rect.top+Rect.bottom)/2),Point(Rect.left,Rect.bottom),
              PointF(0.0f,0.0f),PointF(0.0f,Height/2.0f),
			  Color(m_LrcTransparent,GetRValue(m_FocusMidClr),GetGValue(m_FocusMidClr),GetBValue(m_FocusMidClr)),
			  Color(m_LrcTransparent,GetRValue(m_FocusLowerClr),GetGValue(m_FocusLowerClr),GetBValue(m_FocusLowerClr)));

     if( m_ShadowMode )
     {
         GraphicsPath *ShadowPath = Path.Clone();  
         Matrix ShadowMatrix;  
         ShadowMatrix.Translate( 1, 1 );  
         ShadowPath->Transform(&ShadowMatrix);  
         Graph.FillPath(&ShadowBrush,ShadowPath);
         delete ShadowPath;
     }  

     Graph.SetClip(LowerBox);
     //Graph.FillRectangle(&FocusBrush2, 0, 0, Width, Height);
	 Graph.FillPath(&FocusBrush2,&Path);
     Graph.SetClip(UpperBox);
     //Graph.FillRectangle(&FocusBrush1, 0, 0, Width, Height);
	 Graph.FillPath(&FocusBrush1,&Path);
	 

	 DWORD dwExStyle = GetWindowLong(RendWin,GWL_EXSTYLE);
	 if((dwExStyle&0x80000)!=0x80000)
	    SetWindowLong(RendWin,GWL_EXSTYLE,dwExStyle^0x80000);
     
     SIZE sizeWindow={Rect.right-Rect.left,Rect.bottom-Rect.top};
	 POINT ptSrc={0,0};
	 POINT ptWinPos={Rect.left,Rect.top};
     BOOL Ret = UpdateLayeredWindow( RendWin,WinDC,&ptWinPos,
	                                &sizeWindow,WinMemDC,&ptSrc,0,&m_Blend,2);

     // If it needs copy data, just do it
     if( IsCpyData )
     {
         delete[] *MemBuf;
	     *MemBuf = new BYTE[BitmapSize]();
         memcpy(*MemBuf, MemBits, BitmapSize);
     }

	 ReleaseDC(RendWin,WinDC);
     DeleteObject(BitMap);
	 DeleteDC(WinMemDC);

	 return (Ret != 0);	
}

bool CGdiplusTextRender::PreRender2(HWND RendWin, const tstring & Lrc, bool IsFocus, bool IsCpyData, BYTE ** MemBuf)
{
     // Get the rectangle of window
     RECT Rect;
	 GetWindowRect(RendWin, &Rect);

	 // Get memory dc
	 HDC WinDC = GetDC(RendWin);
	 HDC WinMemDC = CreateCompatibleDC(WinDC);
	     
     // Lookout here, because the length of lrc may be longer than the window's length
     // in pre render stage, we must draw the bitmap according to maximum one between 
     // length of lrc and window's length
     int Width = max(Rect.right-Rect.left, m_LrcBoxSize.cx/*+m_WidthOffset*/);
     int Height = Rect.bottom-Rect.top;

	 // Create Dib section
	 BITMAPINFO  bmi;
	 memset(&bmi, 0, sizeof(bmi));
	 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	 bmi.bmiHeader.biWidth= Width;
	 bmi.bmiHeader.biHeight= Height;
	 bmi.bmiHeader.biPlanes=1;
	 bmi.bmiHeader.biBitCount=32;
	 bmi.bmiHeader.biCompression=BI_RGB;
		
	 BYTE * MemBits = NULL;
	 HBITMAP BitMap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**)&MemBits, 0, 0);
     // Select into memory dc 	
	 SelectObject(WinMemDC,BitMap);

     // Get bitmap's size in BYTE unit
	 int BitmapSize = bmi.bmiHeader.biWidth * bmi.bmiHeader.biHeight * 4;
           
     // Use gdi+  
     Graphics Graph(WinMemDC);
 	 // Set mode
     Graph.SetSmoothingMode(SmoothingModeAntiAlias);
	 Graph.SetInterpolationMode(InterpolationModeHighQualityBicubic);

	 // Paint the background
	 Color BgClr(m_BgTransparent, GetRValue(m_BgClr), GetGValue(m_BgClr), GetBValue(m_BgClr));
	 SolidBrush BrushBkgnd(BgClr);
	 Graph.FillRectangle(&BrushBkgnd,0,0,Width, Height);

     // Add string path
	 FontFamily Font(m_LrcFont.lfFaceName);
	 StringFormat Format;
     Format.SetAlignment(StringAlignmentCenter);
     Format.SetLineAlignment(StringAlignmentNear);
     float Offset = (Height-m_LrcBoxSize.cy)/2.0f;
     RectF LrcBox(0.0f,Offset,Width,Height-Offset);
     RectF UpperBox(0.0f,Offset, LrcBox.Width, Height/2.0f-Offset+1);
     RectF LowerBox(0.0f,Height/2.0f+1, LrcBox.Width, Height/2.0f);

	 GraphicsPath Path;
	 Path.AddString(Lrc.c_str(), Lrc.size(),&Font,
                     GetFontStyle(&m_LrcFont),GetWordSize(m_LrcFont.lfHeight),LrcBox,&Format);

	 // Draw outline
     Pen OutLinePen(Color(155,215,215,215),4);
	 if( m_OutlineMode )
     {
         OutLinePen.SetLineJoin(LineJoinRound);
	     Graph.DrawPath(&OutLinePen,&Path);
     }

     // Shadow brush
     LinearGradientBrush ShadowBrush(
              PointF(0.0f,0.0f),PointF(0.0f,LrcBox.Height),
			  Color(30, 30, 30),
			  Color(30, 30, 30));

     // UnFocus brush
     LinearGradientBrush UnFocusBrush1(
			  //Point(Rect.left,Rect.top),Point(Rect.left,(Rect.top+Rect.bottom)/2),
              PointF(0.0f,Offset),PointF(0.0f,Height/2.0f+1),
			  Color(m_LrcTransparent,GetRValue(m_UnFocusUpperClr),GetGValue(m_UnFocusUpperClr),GetBValue(m_UnFocusUpperClr)),
			  Color(m_LrcTransparent,GetRValue(m_UnFocusMidClr),GetGValue(m_UnFocusMidClr),GetBValue(m_UnFocusMidClr)));
	
	 LinearGradientBrush UnFocusBrush2(
			  //Point(Rect.left,(Rect.top+Rect.bottom)/2),Point(Rect.left,Rect.bottom),
              PointF(0.0f,0.0f),PointF(0.0f,Height/2.0f),
			  Color(m_LrcTransparent,GetRValue(m_UnFocusMidClr),GetGValue(m_UnFocusMidClr),GetBValue(m_UnFocusMidClr)),
			  Color(m_LrcTransparent,GetRValue(m_UnFocusLowerClr),GetGValue(m_UnFocusLowerClr),GetBValue(m_UnFocusLowerClr)));


     if( m_ShadowMode )
     {
         GraphicsPath *ShadowPath = Path.Clone();  
         Matrix ShadowMatrix;  
         ShadowMatrix.Translate( 1, 1 );  
         ShadowPath->Transform(&ShadowMatrix);  
         Graph.FillPath(&ShadowBrush,ShadowPath);
         delete ShadowPath;
     }  

     Graph.SetClip(UpperBox);
     Graph.FillPath(&UnFocusBrush1,&Path);
     Graph.SetClip(LowerBox);
     Graph.FillPath(&UnFocusBrush2,&Path);

     // Set window to be transparent
	 //DWORD dwExStyle = GetWindowLong(RendWin,GWL_EXSTYLE);
	 //if((dwExStyle&0x80000)!=0x80000)
	 //    SetWindowLong(RendWin,GWL_EXSTYLE,dwExStyle^0x80000);

  //   SIZE sizeWindow={Rect.right-Rect.left,Rect.bottom-Rect.top};
	 //POINT ptSrc={0,0};
	 //POINT ptWinPos={Rect.left,Rect.top};
  //   BOOL Ret = UpdateLayeredWindow( RendWin,WinDC,&ptWinPos,
	 //                                 &sizeWindow,WinMemDC,&ptSrc,0,&m_Blend,2);
   
     // If it needs copy data, just do it
     if( IsCpyData )
     {
         delete[] *MemBuf;
	     *MemBuf = new BYTE[BitmapSize]();
         memcpy(*MemBuf, MemBits, BitmapSize);
	 }

	 ReleaseDC(RendWin,WinDC);
     DeleteObject(BitMap);
	 DeleteDC(WinMemDC);

	 return true;	
}

bool CGdiplusTextRender::StartOneLine(HWND RendWin, const tstring & Lrc, bool IsNew)
{
     // Resize the lrc show window
     RECT Rect;
	 GetWindowRect(RendWin, &Rect);
     m_LrcBoxSize = GetLrcBoxSize(RendWin, Lrc);
     if( Rect.bottom-Rect.top < m_LrcBoxSize.cy/*+m_HeightOffset*/ )
         SetWindowPos(RendWin, HWND_TOPMOST, 0, 0, 
                      Rect.right-Rect.left,
                      m_LrcBoxSize.cy/*+m_HeightOffset*/, 
                      SWP_NOZORDER|SWP_NOMOVE|SWP_SHOWWINDOW);

     // If it is a new line, set the move distance to be zero
     if( IsNew )
         m_MoveDist = 0;

     // Do pre render works at first
     PreRender2(RendWin, Lrc, false, true, &m_MemCpyUnFocus);
     PreRender(RendWin, Lrc, true, true, &m_MemCpyFocus);
         
     return true;
}

bool CGdiplusTextRender::RenderBg(HWND RendWin)
{
     if( RendWin == NULL ) return false;
	 
     // Get memory dc
	 HDC WinDC = GetDC(RendWin);
	 HDC WinMemDC = CreateCompatibleDC(WinDC);
	     
     // Get window size
     RECT Rect;
     GetWindowRect(RendWin, &Rect);	     
     int Width = Rect.right-Rect.left;
     int Height = Rect.bottom-Rect.top;

	 // Create Dib section
	 BITMAPINFO  bmi;
	 memset(&bmi, 0, sizeof(bmi));
	 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	 bmi.bmiHeader.biWidth= Width;
	 bmi.bmiHeader.biHeight= Height;
	 bmi.bmiHeader.biPlanes=1;
	 bmi.bmiHeader.biBitCount=32;
	 bmi.bmiHeader.biCompression=BI_RGB;
		
	 BYTE * MemBits = NULL;
	 HBITMAP BitMap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**)&MemBits, 0, 0);
     // Select into memory dc 	
	 SelectObject(WinMemDC,BitMap);

     // Use gdi+  
     Graphics Graph(WinMemDC); 	 // Set mode
     Graph.SetSmoothingMode(SmoothingModeAntiAlias);
	 Graph.SetInterpolationMode(InterpolationModeHighQualityBicubic);

	 // Paint the background
	 Color BgClr(m_BgTransparent, GetRValue(m_BgClr), GetGValue(m_BgClr), GetBValue(m_BgClr));
	 SolidBrush BrushBkgnd(BgClr);
	 Graph.FillRectangle(&BrushBkgnd, 0, 0, Width, Height);

     // Set layed attribution
	 DWORD dwExStyle = GetWindowLong(RendWin, GWL_EXSTYLE);
	 if((dwExStyle&0x80000)!=0x80000)
	     SetWindowLong(RendWin, GWL_EXSTYLE, dwExStyle^0x80000);
     
     // Draw
     SIZE WinSize = {Width, Height};
	 POINT PtSrc = {0,0};
	 POINT PtWinPos = {Rect.left,Rect.top};
     BOOL Ret = UpdateLayeredWindow(RendWin,WinDC,&PtWinPos,
	                                &WinSize,WinMemDC,&PtSrc,0,&m_Blend,2);
     // Release DC
     ReleaseDC(RendWin, WinDC);
     DeleteDC(WinMemDC);
     DeleteObject(BitMap);

     return (Ret != FALSE);  
}

bool CGdiplusTextRender::Render(HWND RendWin, const tstring & Lrc, unsigned int FocusLen)
{
     // Get the size of window
     RECT Rect;
	 GetWindowRect(RendWin, &Rect);

     // If the length of lrc is longer than window's width we should do scroll to show those last words
     //m_LrcBoxSize = GetLrcBoxSize(Lrc);
     int MoveDist = 0;
     if( (m_LrcBoxSize.cx/*+m_WidthOffset*/ > Rect.right-Rect.left) && (FocusLen > (Rect.right-Rect.left)/2) )
         MoveDist = min(FocusLen-(Rect.right-Rect.left)/2, m_LrcBoxSize.cx/*+m_WidthOffset*/-(Rect.right-Rect.left));             
 
     // If being in un-karaoke mode and we have moved to end of lrc, then just return
     if( !m_KaraokeMode && MoveDist == m_MoveDist )
         return true;
     m_MoveDist = MoveDist;

	 // Flush painted lrc buffer to the screen
     return Flush(RendWin, Lrc, FocusLen, false);
}


bool CGdiplusTextRender::Flush(HWND RendWin, const tstring & Lrc, unsigned int FocusLen, bool IsNew)
{
     // If it is needed to change the property, then set New flag and update the lrc painted buffer
     // Set the IsNew flag to false, which flush the poperties, but *Do Not Change* the value of m_MoveDist
     if( IsNew )   StartOneLine(RendWin, Lrc, false);

     // Get the size of window
     RECT Rect;
	 GetWindowRect(RendWin, &Rect);

     // Get memory dc
	 HDC WinDC = GetDC(RendWin);
	 HDC WinMemDC = CreateCompatibleDC(WinDC);
	     
	 // Create Dib section
	 BITMAPINFO  bmi;
	 memset(&bmi, 0, sizeof(bmi));
	 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	 bmi.bmiHeader.biWidth = Rect.right-Rect.left;
	 bmi.bmiHeader.biHeight = Rect.bottom-Rect.top;
	 bmi.bmiHeader.biPlanes =1;
	 bmi.bmiHeader.biBitCount =32;
	 bmi.bmiHeader.biCompression = BI_RGB;

	 // Create dib section and Select it into memory dc 	
	 BYTE * MemBits = NULL;
	 HBITMAP BitMap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**)&MemBits, 0, 0);
	 SelectObject(WinMemDC,BitMap);

     //if( (m_LrcBoxSize.cx+m_WidthOffset > Rect.right-Rect.left) && (FocusLen > (Rect.right-Rect.left)/2) )
     //{
     //    // First copy background color
     //    memcpy(MemBits, m_MemCpyBg, m_BgBitmapSize);
     //    MoveDist = min(FocusLen-(Rect.right-Rect.left)/2, m_LrcBoxSize.cx+m_WidthOffset-(Rect.right-Rect.left));             
     //}

     // Copy gdi+ drawing data to show like karaoke mode
     int OffsetCpyFactor = max(bmi.bmiHeader.biWidth, m_LrcBoxSize.cx/*+m_WidthOffset*/)*4;
     if( m_KaraokeMode )
     {
         for( int i = 0; i < bmi.bmiHeader.biHeight; ++i )
         {
              int Offset = i*bmi.bmiHeader.biWidth*4;
              int OffsetCpy = OffsetCpyFactor*i;
              memcpy(MemBits+Offset, m_MemCpyFocus+OffsetCpy+m_MoveDist*4, (FocusLen-m_MoveDist)*4);
              memcpy(MemBits+Offset+(FocusLen-m_MoveDist)*4, m_MemCpyUnFocus+OffsetCpy+FocusLen*4, (bmi.bmiHeader.biWidth-FocusLen+m_MoveDist)*4);
         }
     }else {
         for( int i = 0; i < bmi.bmiHeader.biHeight; ++i )
         {
              int Offset = i*bmi.bmiHeader.biWidth*4;
              int OffsetCpy = OffsetCpyFactor*i;
              memcpy(MemBits+Offset, m_MemCpyFocus+OffsetCpy+m_MoveDist*4, bmi.bmiHeader.biWidth*4);
         }
     }


     // Update the layered window
     POINT ptWinPos={Rect.left,Rect.top};
     SIZE  sizeWindow={Rect.right-Rect.left,Rect.bottom-Rect.top};
	 POINT ptSrc={0,0};

	 BOOL Ret = UpdateLayeredWindow(RendWin,WinDC,&ptWinPos, &sizeWindow,
                                    WinMemDC,&ptSrc,0,&m_Blend,2);

     // Release gdi object 
     ReleaseDC(RendWin,WinDC);
	 DeleteObject(BitMap);
	 DeleteDC(WinMemDC);

	 return (Ret != 0);	
}